"""
告警通知服务
实现多渠道通知功能
"""
import logging
import json
import requests
import base64
import io
from typing import List, Dict, Any
from datetime import datetime, timedelta
from django.utils import timezone
from django.core.mail import send_mail
from django.conf import settings

from ..models import MonitoringAlert, NotificationChannel, AlertNotification

logger = logging.getLogger(__name__)

try:
    import matplotlib
    matplotlib.use('Agg')  # 使用非交互式后端
    import matplotlib.pyplot as plt
    import matplotlib.dates as mdates
    from matplotlib.font_manager import FontProperties
    import numpy as np
    MATPLOTLIB_AVAILABLE = True
except ImportError:
    MATPLOTLIB_AVAILABLE = False
    logger.warning("matplotlib未安装，无法生成监控图表")


class NotificationService:

    def _get_monitoring_chart_icon(self, host_data=None):
        """获取监控图表图标（使用静态图标避免URL长度限制）"""
        # 根据监控状态返回不同的图标
        try:
            if host_data:
                # 如果有真实数据，根据状态选择图标
                cpu_avg = sum(host_data.get('cpu_history', [30])) / len(host_data.get('cpu_history', [30]))
                if cpu_avg > 80:
                    return "https://img.icons8.com/color/96/000000/high-priority.png"  # 高负载红色
                elif cpu_avg > 60:
                    return "https://img.icons8.com/color/96/000000/medium-priority.png"  # 中负载橙色
                else:
                    return "https://img.icons8.com/color/96/000000/line-chart.png"  # 正常绿色图表
            else:
                # 默认使用监控图表图标
                return "https://img.icons8.com/color/96/000000/line-chart.png"

        except Exception as e:
            logger.error(f"获取监控图标失败: {e}")
            # 返回默认图标
            return "https://img.icons8.com/color/96/000000/line-chart.png"
    """通知服务"""

    def __init__(self):
        self.max_retry_count = 3
        self.retry_delay = 300  # 5分钟重试间隔

    def send_alert_notifications(self, alerts: List[MonitoringAlert]):
        """发送告警通知"""
        if not alerts:
            return

        # 获取活跃的通知渠道
        active_channels = NotificationChannel.objects.filter(is_active=True)

        for alert in alerts:
            for channel in active_channels:
                try:
                    self._send_single_notification(alert, channel)
                except Exception as e:
                    logger.error(f"发送通知失败 - 告警:{alert.id}, 渠道:{channel.name}: {e}")

    def _send_single_notification(self, alert: MonitoringAlert, channel: NotificationChannel):
        """发送单个通知"""
        # 检查是否需要去重
        if self._should_suppress_notification(alert, channel):
            logger.info(f"告警通知被抑制 - 告警:{alert.id}, 渠道:{channel.name}")
            return

        # 生成通知内容
        subject, content = self._generate_notification_content(alert)

        # 创建通知记录
        notification = AlertNotification.objects.create(
            alert=alert,
            channel=channel,
            subject=subject,
            content=content,
            status='pending'
        )

        try:
            # 根据渠道类型发送通知
            if channel.channel_type == 'email':
                self._send_email_notification(notification)
            elif channel.channel_type == 'sms':
                self._send_sms_notification(notification)
            elif channel.channel_type == 'webhook':
                self._send_webhook_notification(notification)
            elif channel.channel_type == 'dingtalk':
                self._send_dingtalk_notification(notification)
            elif channel.channel_type == 'wechat':
                self._send_wechat_notification(notification)
            elif channel.channel_type == 'slack':
                self._send_slack_notification(notification)
            else:
                logger.warning(f"不支持的通知渠道类型: {channel.channel_type}")
                notification.status = 'failed'
                notification.error_message = f"不支持的渠道类型: {channel.channel_type}"
                notification.save()
                return

            # 标记为已发送
            notification.status = 'sent'
            notification.sent_at = timezone.now()
            notification.save()

        except Exception as e:
            # 标记为失败
            notification.status = 'failed'
            notification.error_message = str(e)
            notification.save()

            # 如果重试次数未达到上限，标记为重试
            if notification.retry_count < self.max_retry_count:
                notification.status = 'retry'
                notification.retry_count += 1
                notification.save()

            raise e

    def _should_suppress_notification(self, alert: MonitoringAlert, channel: NotificationChannel) -> bool:
        """判断是否应该抑制通知（去重）"""
        # 对于测试告警，不进行抑制
        if hasattr(alert, 'raw_data') and alert.raw_data and alert.raw_data.get('source') == 'sse_test':
            logger.info(f"SSE测试告警不进行抑制 - 告警:{alert.id}, 渠道:{channel.name}")
            return False

        # 检查最近10分钟内是否已发送过相同告警的通知（缩短时间窗口）
        recent_time = timezone.now() - timedelta(minutes=10)

        # 更精确的去重条件：相同主机 + 相同标题 + 相同严重程度 + 活跃状态
        # 只抑制活跃状态的重复告警，已确认的告警如果再次触发应该重新推送
        recent_notifications = AlertNotification.objects.filter(
            alert__host=alert.host,
            alert__title=alert.title,
            alert__severity=alert.severity,
            alert__status='active',  # 只检查活跃状态的告警
            channel=channel,
            created_at__gte=recent_time,
            status__in=['sent']  # 只检查已发送的，不包括pending状态
        ).exists()

        if recent_notifications:
            logger.info(f"告警通知被抑制（30分钟内已发送相同告警） - 告警:{alert.id}, 渠道:{channel.name}")

        return recent_notifications

    def _generate_notification_content(self, alert: MonitoringAlert) -> tuple:
        """生成通知内容"""
        severity_map = {
            'disaster': '🔴 灾难',
            'high': '🟠 严重',
            'average': '🟡 一般严重',
            'warning': '🟡 警告',
            'information': '🔵 信息',
            'not_classified': '⚪ 未分类'
        }

        severity_text = severity_map.get(alert.severity, alert.severity)
        subject = f"🚨 [{severity_text}] {alert.title}"

        host_name = alert.host.hostname if alert.host else "未知主机"

        # 简单文本内容（用于非邮件通知）
        content = f"""
告警详情：
- 主机：{host_name}
- 告警标题：{alert.title}
- 严重级别：{severity_text}
- 状态：{alert.get_status_display()}
- 触发时间：{alert.event_time.strftime('%Y-%m-%d %H:%M:%S')}
- Zabbix服务器：{alert.zabbix_server.name}

描述：{alert.description}
        """.strip()

        return subject, content

    def _generate_html_email_content(self, alert: MonitoringAlert) -> tuple:
        """生成HTML邮件内容"""
        from django.utils import timezone

        severity_map = {
            'disaster': '🔴 灾难',
            'high': '🟠 严重',
            'average': '🟡 一般严重',
            'warning': '🟡 警告',
            'information': '🔵 信息',
            'not_classified': '⚪ 未分类'
        }

        # 根据严重级别选择颜色和图标
        severity_config = {
            'disaster': {'color': '#dc2626', 'icon': '🔴', 'bg': '#fef2f2'},
            'high': {'color': '#ea580c', 'icon': '🟠', 'bg': '#fff7ed'},
            'average': {'color': '#ca8a04', 'icon': '🟡', 'bg': '#fefce8'},
            'warning': {'color': '#ca8a04', 'icon': '🟡', 'bg': '#fefce8'},
            'information': {'color': '#2563eb', 'icon': '🔵', 'bg': '#eff6ff'},
            'not_classified': {'color': '#6b7280', 'icon': '⚪', 'bg': '#f9fafb'}
        }

        config = severity_config.get(alert.severity, severity_config['average'])
        severity_text = severity_map.get(alert.severity, alert.severity)

        subject = f"🚨 [{severity_text}] {alert.title}"
        host_name = alert.host.hostname if alert.host else "未知主机"

        # 完全按照测试邮件样式设计告警邮件
        content = f"""
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <style>
        body {{
            font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            margin: 0;
            padding: 20px;
        }}
        .container {{
            max-width: 600px;
            margin: 0 auto;
            background: rgba(255, 255, 255, 0.95);
            border-radius: 15px;
            box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1);
            overflow: hidden;
            backdrop-filter: blur(10px);
        }}
        .header {{
            background: linear-gradient(135deg, #a4f4ba 0%, #22c55e 100%);
            color: white;
            padding: 30px;
            text-align: center;
        }}
        .header h1 {{
            margin: 0;
            font-size: 28px;
            font-weight: 600;
            text-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
        }}
        .header .icon {{
            font-size: 48px;
            margin-bottom: 10px;
        }}
        .content {{
            padding: 40px;
        }}
        .success-badge {{
            background: linear-gradient(135deg, {config['color']}, {config['color']}dd);
            color: white;
            padding: 15px 25px;
            border-radius: 50px;
            display: inline-block;
            font-weight: 600;
            margin-bottom: 30px;
            box-shadow: 0 4px 15px {config['color']}33;
        }}
        .config-info {{
            background: #f8fafc;
            border-radius: 12px;
            padding: 25px;
            margin: 20px 0;
            border-left: 4px solid #a4f4ba;
        }}
        .config-item {{
            display: flex;
            justify-content: space-between;
            padding: 8px 0;
            border-bottom: 1px solid #e2e8f0;
        }}
        .config-item:last-child {{
            border-bottom: none;
        }}
        .config-label {{
            font-weight: 600;
            color: #475569;
        }}
        .config-value {{
            color: #1e293b;
            font-family: 'Courier New', monospace;
        }}
        .footer {{
            background: #f1f5f9;
            padding: 20px;
            text-align: center;
            color: #64748b;
            font-size: 14px;
        }}
        .timestamp {{
            background: linear-gradient(135deg, #667eea, #764ba2);
            color: white;
            padding: 10px 20px;
            border-radius: 25px;
            display: inline-block;
            margin-top: 20px;
            font-weight: 500;
        }}
    </style>
</head>
<body>
    <div class="container">
        <div class="header">
            <div class="icon">🚨</div>
            <h1>DevOps监控系统</h1>
            <p style="margin: 10px 0 0 0; opacity: 0.9;">系统告警通知</p>
        </div>

        <div class="content">
            <div class="success-badge">
                {config['icon']} {severity_text} - {alert.title}
            </div>

            <p style="font-size: 16px; line-height: 1.6; color: #334155;">
                检测到系统告警，请及时处理。以下是告警的详细信息：
            </p>

            <div class="config-info">
                <h3 style="margin-top: 0; color: #1e293b;">📊 告警详情</h3>
                <div class="config-item">
                    <span class="config-label">告警名称:</span>
                    <span class="config-value">{alert.title}</span>
                </div>
                <div class="config-item">
                    <span class="config-label">严重级别:</span>
                    <span class="config-value">{config['icon']} {severity_text}</span>
                </div>
                <div class="config-item">
                    <span class="config-label">主机名称:</span>
                    <span class="config-value">{host_name}</span>
                </div>
                <div class="config-item">
                    <span class="config-label">告警状态:</span>
                    <span class="config-value">{'🔴 触发中' if alert.status == 'active' else '🟢 已恢复'}</span>
                </div>
                <div class="config-item">
                    <span class="config-label">发生时间:</span>
                    <span class="config-value">{alert.event_time.strftime('%Y-%m-%d %H:%M:%S')}</span>
                </div>
                <div class="config-item">
                    <span class="config-label">告警描述:</span>
                    <span class="config-value">{alert.description or '无描述信息'}</span>
                </div>
            </div>

            <p style="font-size: 14px; color: #64748b; line-height: 1.6;">
                💡 <strong>提示:</strong> 请及时处理告警，确保系统稳定运行。如有疑问，请联系系统管理员。
            </p>

            <div class="timestamp">
                🕒 发送时间: {timezone.now().strftime('%Y-%m-%d %H:%M:%S')}
            </div>
        </div>

        <div class="footer">
            <p>此邮件由 DevOps 监控系统自动发送，请勿回复。</p>
            <p style="margin: 5px 0 0 0;">如有问题，请联系系统管理员。</p>
        </div>
    </div>
</body>
</html>
        """.strip()

        return subject, content

    def _generate_email_content(self, alert: MonitoringAlert, subject: str, content: str):
        """生成邮件的HTML和纯文本内容"""
        from django.utils import timezone

        # 获取主机信息
        hostname = alert.host.hostname if alert.host else "未知主机"
        host_ip = ""
        if alert.host:
            # 使用正确的字段名
            if hasattr(alert.host, 'private_ip') and alert.host.private_ip:
                host_ip = alert.host.private_ip
            elif hasattr(alert.host, 'public_ip') and alert.host.public_ip:
                host_ip = alert.host.public_ip
            elif hasattr(alert.host, 'ip') and alert.host.ip:
                host_ip = alert.host.ip

        # 严重程度颜色映射
        severity_colors = {
            'critical': '#dc2626',  # 红色
            'high': '#ea580c',      # 橙色
            'warning': '#d97706',   # 黄色
            'info': '#2563eb',      # 蓝色
            'low': '#16a34a'        # 绿色
        }
        severity_color = severity_colors.get(alert.severity, '#6b7280')

        # HTML内容
        html_content = f"""
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <style>
        body {{
            font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            margin: 0;
            padding: 20px;
        }}
        .container {{
            max-width: 600px;
            margin: 0 auto;
            background: rgba(255, 255, 255, 0.95);
            border-radius: 15px;
            box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1);
            overflow: hidden;
            backdrop-filter: blur(10px);
        }}
        .header {{
            background: linear-gradient(135deg, {severity_color}, {severity_color}dd);
            color: white;
            padding: 30px;
            text-align: center;
        }}
        .header h1 {{
            margin: 0;
            font-size: 28px;
            font-weight: 600;
            text-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
        }}
        .header .icon {{
            font-size: 48px;
            margin-bottom: 10px;
        }}
        .content {{
            padding: 40px;
        }}
        .alert-badge {{
            background: linear-gradient(135deg, {severity_color}, {severity_color}dd);
            color: white;
            padding: 15px 25px;
            border-radius: 50px;
            display: inline-block;
            font-weight: 600;
            margin-bottom: 30px;
            box-shadow: 0 4px 15px rgba(220, 38, 38, 0.3);
        }}
        .alert-info {{
            background: #f8fafc;
            border-radius: 12px;
            padding: 25px;
            margin: 20px 0;
            border-left: 4px solid {severity_color};
        }}
        .alert-item {{
            display: flex;
            justify-content: space-between;
            padding: 8px 0;
            border-bottom: 1px solid #e2e8f0;
        }}
        .alert-item:last-child {{
            border-bottom: none;
        }}
        .alert-label {{
            font-weight: 600;
            color: #475569;
        }}
        .alert-value {{
            color: #1e293b;
            font-family: 'Courier New', monospace;
        }}
        .footer {{
            background: #f1f5f9;
            padding: 20px;
            text-align: center;
            color: #64748b;
            font-size: 14px;
        }}
        .timestamp {{
            background: linear-gradient(135deg, #667eea, #764ba2);
            color: white;
            padding: 10px 20px;
            border-radius: 25px;
            display: inline-block;
            margin-top: 20px;
            font-weight: 500;
        }}
    </style>
</head>
<body>
    <div class="container">
        <div class="header">
            <div class="icon">🚨</div>
            <h1>DevOps监控系统</h1>
            <p style="margin: 10px 0 0 0; opacity: 0.9;">告警通知</p>
        </div>

        <div class="content">
            <div class="alert-badge">
                🔥 {alert.get_severity_display()} 告警
            </div>

            <p style="font-size: 16px; line-height: 1.6; color: #334155;">
                <strong>{subject}</strong>
            </p>

            <div class="alert-info">
                <h3 style="margin-top: 0; color: #1e293b;">📊 告警详情</h3>
                <div class="alert-item">
                    <span class="alert-label">告警标题:</span>
                    <span class="alert-value">{alert.title}</span>
                </div>
                <div class="alert-item">
                    <span class="alert-label">主机名称:</span>
                    <span class="alert-value">{hostname}</span>
                </div>
                {f'<div class="alert-item"><span class="alert-label">主机IP:</span><span class="alert-value">{host_ip}</span></div>' if host_ip else ''}
                <div class="alert-item">
                    <span class="alert-label">严重程度:</span>
                    <span class="alert-value">{alert.get_severity_display()}</span>
                </div>
                <div class="alert-item">
                    <span class="alert-label">告警状态:</span>
                    <span class="alert-value">{alert.get_status_display()}</span>
                </div>
                <div class="alert-item">
                    <span class="alert-label">发生时间:</span>
                    <span class="alert-value">{alert.event_time.strftime('%Y-%m-%d %H:%M:%S') if alert.event_time else '未知'}</span>
                </div>
            </div>

            <p style="font-size: 14px; color: #64748b; line-height: 1.6;">
                {content}
            </p>

            <div class="timestamp">
                🕒 通知时间: {timezone.now().strftime('%Y-%m-%d %H:%M:%S')}
            </div>
        </div>

        <div class="footer">
            <p>此邮件由 DevOps 监控系统自动发送，请勿回复。</p>
            <p style="margin: 5px 0 0 0;">如有问题，请联系系统管理员。</p>
        </div>
    </div>
</body>
</html>
"""

        # 纯文本内容
        text_content = f"""
🚨 DevOps监控系统 - 告警通知

🔥 {alert.get_severity_display()} 告警

{subject}

告警详情:
- 告警标题: {alert.title}
- 主机名称: {hostname}
{f'- 主机IP: {host_ip}' if host_ip else ''}
- 严重程度: {alert.get_severity_display()}
- 告警状态: {alert.get_status_display()}
- 发生时间: {alert.event_time.strftime('%Y-%m-%d %H:%M:%S') if alert.event_time else '未知'}
- 通知时间: {timezone.now().strftime('%Y-%m-%d %H:%M:%S')}

{content}

此邮件由 DevOps 监控系统自动发送，请勿回复。
"""

        return html_content, text_content

    def _send_email_notification(self, notification: AlertNotification):
        """发送邮件通知"""
        try:
            channel = notification.channel
            subject = notification.subject
            content = notification.content

            # 生成HTML和纯文本内容
            html_content, text_content = self._generate_email_content(notification.alert, subject, content)

            # 获取收件人列表
            config = channel.config
            recipients = config.get('recipients', [])
            if not recipients:
                # 如果没有配置收件人，使用发件人邮箱作为收件人
                from_email = config.get('from_email')
                if from_email:
                    recipients = [from_email]
                else:
                    raise ValueError("未配置收件人列表且无法获取发件人邮箱")

            logger.info(f"开始发送邮件通知 - 渠道: {channel.name}, 收件人: {recipients}")

            # 使用统一的邮件发送方法
            self._send_html_email_with_config(channel, subject, html_content, text_content, recipients)

            logger.info(f"邮件通知发送成功 - 渠道: {channel.name}")

        except Exception as e:
            logger.error(f"邮件通知发送失败 - 渠道: {notification.channel.name}, 错误: {e}")
            raise

    def _send_html_email_with_config(self, channel, subject, html_content, text_content, recipients):
        """使用EmailConfig发送邮件"""
        try:
            import smtplib
            from email.mime.text import MIMEText
            from email.mime.multipart import MIMEMultipart
            from ..models import EmailConfig

            # 优先使用通道配置中的直接SMTP设置，如果没有则使用EmailConfig
            config = channel.config
            email_config_id = config.get('email_config_id')

            # 检查通知渠道是否有直接的SMTP配置
            has_direct_smtp = all([
                config.get('smtp_server'),
                config.get('username'),
                config.get('password'),
                config.get('from_email')
            ])

            if has_direct_smtp:
                # 优先使用通知渠道的直接SMTP配置
                smtp_server = config.get('smtp_server')
                smtp_port = config.get('smtp_port', 587)
                username = config.get('username')
                password = config.get('password')
                from_email = config.get('from_email')
                use_ssl = config.get('use_ssl', False)
                use_tls = config.get('use_tls', True)
                logger.info(f"使用通知渠道直接配置: {smtp_server}:{smtp_port}, 用户: {username}")
            elif email_config_id:
                # 使用指定的EmailConfig配置
                try:
                    email_config = EmailConfig.objects.get(id=email_config_id, is_active=True)
                    smtp_server = email_config.smtp_server
                    smtp_port = email_config.smtp_port
                    username = email_config.username
                    password = email_config.password
                    from_email = email_config.from_email
                    use_tls = email_config.use_tls
                    use_ssl = email_config.use_ssl
                    logger.info(f"使用指定EmailConfig配置: {smtp_server}:{smtp_port}, 用户: {username}")
                except EmailConfig.DoesNotExist:
                    # 如果指定的配置不存在，使用默认配置
                    email_config = EmailConfig.objects.filter(is_default=True, is_active=True).first()
                    if not email_config:
                        raise ValueError("指定的邮件配置不存在，且没有默认邮件配置")
                    smtp_server = email_config.smtp_server
                    smtp_port = email_config.smtp_port
                    username = email_config.username
                    password = email_config.password
                    from_email = email_config.from_email
                    use_tls = email_config.use_tls
                    use_ssl = email_config.use_ssl
                    logger.info(f"使用默认EmailConfig配置: {smtp_server}:{smtp_port}, 用户: {username}")
            else:
                # 最后尝试使用默认邮件配置
                email_config = EmailConfig.objects.filter(is_default=True, is_active=True).first()
                if not email_config:
                    raise ValueError("未找到可用的邮件配置，请先配置邮件服务器或通知渠道")
                smtp_server = email_config.smtp_server
                smtp_port = email_config.smtp_port
                username = email_config.username
                password = email_config.password
                from_email = email_config.from_email
                use_tls = email_config.use_tls
                use_ssl = email_config.use_ssl
                logger.info(f"使用默认EmailConfig配置: {smtp_server}:{smtp_port}, 用户: {username}")

            # 创建SMTP连接
            server = None
            try:
                logger.info(f"尝试连接SMTP服务器: {smtp_server}:{smtp_port}, SSL={use_ssl}, TLS={use_tls}")

                if use_ssl:
                    server = smtplib.SMTP_SSL(smtp_server, smtp_port)
                    logger.info("SSL连接建立成功")
                else:
                    server = smtplib.SMTP(smtp_server, smtp_port)
                    logger.info("SMTP连接建立成功")
                    if use_tls:
                        server.starttls()
                        logger.info("TLS加密启用成功")

                logger.info(f"尝试登录用户: {username}")
                try:
                    server.login(username, password)
                    logger.info("SMTP登录成功")
                except smtplib.SMTPAuthenticationError as e:
                    logger.error(f"SMTP认证失败: {e}")
                    if 'qq.com' in smtp_server.lower():
                        raise Exception("QQ邮箱认证失败。请确保使用授权码而不是登录密码。获取授权码：QQ邮箱设置 -> 账户 -> POP3/IMAP/SMTP/Exchange/CardDAV/CalDAV服务 -> 生成授权码")
                    else:
                        raise Exception(f"邮箱认证失败: {e}")
                except Exception as e:
                    logger.error(f"SMTP登录异常: {e}")
                    error_msg = str(e).lower()
                    if 'connection unexpectedly closed' in error_msg:
                        if 'qq.com' in smtp_server.lower():
                            raise Exception("QQ邮箱连接被拒绝。请检查：1) 使用授权码而不是登录密码；2) 确保已开启POP3/SMTP服务。获取授权码：QQ邮箱设置 -> 账户 -> POP3/IMAP/SMTP服务")
                        else:
                            raise Exception(f"邮箱服务器拒绝连接，请检查用户名和密码: {e}")
                    else:
                        raise

                # 发送给每个收件人
                for recipient_email in recipients:
                    msg = MIMEMultipart()
                    msg['From'] = from_email
                    msg['To'] = recipient_email
                    msg['Subject'] = subject

                    # 添加纯文本版本
                    msg.attach(MIMEText(text_content, 'plain', 'utf-8'))
                    # 添加HTML版本
                    msg.attach(MIMEText(html_content, 'html', 'utf-8'))

                    # 发送邮件
                    server.send_message(msg)
                    logger.info(f"邮件发送成功: {recipient_email}")

            finally:
                if server:
                    try:
                        server.quit()
                    except Exception as e:
                        logger.warning(f"关闭SMTP连接时出错: {e}")

        except Exception as e:
            logger.error(f"邮件发送失败: {e}")
            raise

    def _send_webhook_notification(self, notification: AlertNotification):
        """发送Webhook通知"""
        config = notification.channel.config
        url = config.get('url')

        if not url:
            raise ValueError("Webhook通知渠道未配置URL")

        # 构造请求数据
        data = {
            'alert_id': notification.alert.id,
            'title': notification.alert.title,
            'hostname': notification.alert.host.hostname if notification.alert.host else "未知主机",
            'severity': notification.alert.severity,
            'status': notification.alert.status,
            'event_time': notification.alert.event_time.isoformat(),
            'description': notification.alert.description,
            'subject': notification.subject,
            'content': notification.content
        }

        # 发送请求
        headers = config.get('headers', {})
        headers.setdefault('Content-Type', 'application/json')

        response = requests.post(
            url,
            json=data,
            headers=headers,
            timeout=30
        )

        if not response.ok:
            raise Exception(f"Webhook请求失败: {response.status_code} {response.text}")

    def _send_dingtalk_notification(self, notification: AlertNotification):
        """发送钉钉通知"""
        config = notification.channel.config
        webhook_url = config.get('webhook_url')

        if not webhook_url:
            raise ValueError("钉钉通知渠道未配置Webhook URL")

        # 处理签名
        import time
        import hmac
        import hashlib
        import base64
        import urllib.parse

        secret = config.get('secret')
        if secret:
            timestamp = str(round(time.time() * 1000))
            secret_enc = secret.encode('utf-8')
            string_to_sign = '{}\n{}'.format(timestamp, secret)
            string_to_sign_enc = string_to_sign.encode('utf-8')
            hmac_code = hmac.new(secret_enc, string_to_sign_enc, digestmod=hashlib.sha256).digest()
            sign = urllib.parse.quote_plus(base64.b64encode(hmac_code))
            webhook_url = f"{webhook_url}&timestamp={timestamp}&sign={sign}"

        # 构造优化的钉钉消息
        severity_icons = {
            'disaster': '🔴',
            'high': '🟠',
            'average': '🟡',
            'warning': '🟡',
            'information': '🔵',
            'not_classified': '⚪'
        }

        icon = severity_icons.get(notification.alert.severity, '⚪')

        # 获取关键词配置
        keyword = config.get('keyword', 'devops')  # 默认使用devops

        # 根据严重级别设置图标和颜色
        severity_info = {
            'disaster': {'emoji': '🔴', 'color': '#FF0000', 'level': '灾难'},
            'high': {'emoji': '🟠', 'color': '#FF8C00', 'level': '严重'},
            'average': {'emoji': '🟡', 'color': '#FFD700', 'level': '一般'},
            'warning': {'emoji': '🟡', 'color': '#FFD700', 'level': '警告'},
            'information': {'emoji': '🔵', 'color': '#0080FF', 'level': '信息'},
            'not_classified': {'emoji': '⚪', 'color': '#808080', 'level': '未分类'}
        }

        severity = severity_info.get(notification.alert.severity, severity_info['not_classified'])

        # 使用markdown格式创建丰富的钉钉告警消息
        formatted_content = f"""{keyword}

# {severity['emoji']} {severity['level']}告警 - {notification.alert.host.hostname}

![告警图标](https://img.icons8.com/color/48/000000/error--v1.png)

---

## 🚨 告警概要

**{notification.subject}**

---

## 📋 详细信息

| 项目 | 内容 |
|------|------|
| **🖥️ 主机名** | {notification.alert.host.hostname if notification.alert.host else "未知主机"} |
| **🌐 IP地址** | {notification.alert.host.private_ip if notification.alert.host else "未知"} |
| **⚠️ 严重级别** | {severity['emoji']} {notification.alert.get_severity_display()} |
| **📊 告警状态** | {notification.alert.get_status_display()} |
| **⏰ 触发时间** | {notification.alert.event_time.strftime('%Y-%m-%d %H:%M:%S')} |
| **🖥️ 监控服务器** | {notification.alert.zabbix_server.name} |

---

## 📝 告警描述

> ### {notification.alert.description}

---

## 🔧 处理建议

> 1. **立即检查** 受影响的主机状态
> 2. **查看日志** 确认具体问题原因
> 3. **及时处理** 避免影响扩大
> 4. **更新状态** 处理完成后确认告警

---

*🤖 DevOps监控系统自动发送 | {notification.alert.event_time.strftime('%Y-%m-%d %H:%M:%S')}*"""

        data = {
            "msgtype": "markdown",
            "markdown": {
                "title": f'{icon} {notification.alert.get_severity_display()}告警',
                "text": formatted_content
            }
        }

        # 如果配置了@所有人或特定用户
        if config.get('at_all') or config.get('at_mobiles'):
            data["at"] = {
                "isAtAll": config.get('at_all', False),
                "atMobiles": config.get('at_mobiles', [])
            }

        response = requests.post(
            webhook_url,
            json=data,
            headers={'Content-Type': 'application/json'},
            timeout=30
        )

        if not response.ok:
            raise Exception(f"钉钉通知发送失败: {response.status_code} {response.text}")

        result = response.json()
        if result.get('errcode') != 0:
            raise Exception(f"钉钉通知发送失败: {result.get('errmsg')}")

    def _send_wechat_notification(self, notification: AlertNotification):
        """发送企业微信通知"""
        config = notification.channel.config
        webhook_url = config.get('webhook_url')

        if not webhook_url:
            raise ValueError("企业微信通知渠道未配置Webhook URL")

        # 构造优化的企业微信消息
        severity_icons = {
            'disaster': '🔴',
            'high': '🟠',
            'average': '🟡',
            'warning': '🟡',
            'information': '🔵',
            'not_classified': '⚪'
        }

        icon = severity_icons.get(notification.alert.severity, '⚪')

        # 尝试获取主机监控数据来生成图表
        host_data = None
        try:
            if hasattr(notification.alert, 'host') and notification.alert.host:
                # 这里可以添加获取真实监控数据的逻辑
                # host_data = self._get_host_monitoring_data(notification.alert.host)
                pass
        except Exception as e:
            logger.warning(f"获取主机监控数据失败: {e}")

        # 获取监控图表图标
        pic_url = self._get_monitoring_chart_icon(host_data)

        data = {
            "msgtype": "news",
            "news": {
                "articles": [
                    {
                        "title": f"{icon} {notification.alert.get_severity_display()}告警 - {notification.alert.host.hostname if notification.alert.host else '未知主机'}",
                        "description": f"""🚨 {notification.subject}

🖥️ 主机信息：
• 主机名：{notification.alert.host.hostname if notification.alert.host else "未知主机"}
• IP地址：{notification.alert.host.private_ip if notification.alert.host else "未知"}
• 严重级别：{notification.alert.get_severity_display()}
• 告警状态：{notification.alert.get_status_display()}

⏰ 时间信息：
• 触发时间：{notification.alert.event_time.strftime('%Y-%m-%d %H:%M:%S')}

📝 告警描述：
{notification.alert.description}

🖥️ 监控服务器：{notification.alert.zabbix_server.name}""",
                        "url": "https://work.weixin.qq.com",
                        "picurl": pic_url
                    }
                ]
            }
        }

        # 如果配置了@特定用户
        if config.get('mentioned_list'):
            data["template_card"]["mentioned_list"] = config.get('mentioned_list')

        response = requests.post(
            webhook_url,
            json=data,
            headers={'Content-Type': 'application/json'},
            timeout=30
        )

        if not response.ok:
            raise Exception(f"企业微信通知发送失败: {response.status_code} {response.text}")

        result = response.json()
        if result.get('errcode') != 0:
            raise Exception(f"企业微信通知发送失败: {result.get('errmsg')}")

    def _send_sms_notification(self, notification: AlertNotification):
        """发送短信通知"""
        config = notification.channel.config

        # 这里可以集成不同的短信服务商
        # 如阿里云短信、腾讯云短信等
        provider = config.get('provider', 'aliyun')

        if provider == 'aliyun':
            self._send_aliyun_sms(notification, config)
        elif provider == 'tencent':
            self._send_tencent_sms(notification, config)
        else:
            raise ValueError(f"不支持的短信服务商: {provider}")

    def _send_aliyun_sms(self, notification: AlertNotification, config: Dict):
        """发送阿里云短信"""
        # 这里需要安装阿里云SDK: pip install alibabacloud_dysmsapi20170525
        # 简化实现，实际使用时需要配置阿里云SDK

        phone_numbers = config.get('phone_numbers', [])
        if not phone_numbers:
            raise ValueError("短信通知渠道未配置手机号码")

        # 模拟发送短信
        logger.info(f"模拟发送阿里云短信到 {phone_numbers}: {notification.subject}")

        # 实际实现时的代码示例：
        # from alibabacloud_dysmsapi20170525.client import Client
        # from alibabacloud_tea_openapi import models as open_api_models
        #
        # config_obj = open_api_models.Config(
        #     access_key_id=config.get('access_key_id'),
        #     access_key_secret=config.get('access_key_secret')
        # )
        # client = Client(config_obj)
        #
        # for phone in phone_numbers:
        #     request = SendSmsRequest()
        #     request.phone_numbers = phone
        #     request.sign_name = config.get('sign_name')
        #     request.template_code = config.get('template_code')
        #     request.template_param = json.dumps({
        #         'title': notification.alert.title,
        #         'hostname': notification.alert.host.hostname
        #     })
        #     response = client.send_sms(request)

    def _send_tencent_sms(self, notification: AlertNotification, config: Dict):
        """发送腾讯云短信"""
        # 这里需要安装腾讯云SDK: pip install tencentcloud-sdk-python
        phone_numbers = config.get('phone_numbers', [])
        if not phone_numbers:
            raise ValueError("短信通知渠道未配置手机号码")

        # 模拟发送短信
        logger.info(f"模拟发送腾讯云短信到 {phone_numbers}: {notification.subject}")

    def _send_slack_notification(self, notification: AlertNotification):
        """发送Slack通知"""
        config = notification.channel.config
        webhook_url = config.get('webhook_url')

        if not webhook_url:
            raise ValueError("Slack通知渠道未配置Webhook URL")

        # 构造Slack消息
        severity_colors = {
            'disaster': '#FF0000',    # 红色
            'high': '#FF8C00',        # 橙色
            'average': '#FFD700',     # 金色
            'warning': '#FFFF00',     # 黄色
            'information': '#0000FF', # 蓝色
            'not_classified': '#808080' # 灰色
        }

        color = severity_colors.get(notification.alert.severity, '#808080')

        data = {
            "attachments": [
                {
                    "color": color,
                    "title": notification.subject,
                    "text": notification.content,
                    "fields": [
                        {
                            "title": "主机",
                            "value": notification.alert.host.hostname if notification.alert.host else "未知主机",
                            "short": True
                        },
                        {
                            "title": "严重程度",
                            "value": notification.alert.get_severity_display(),
                            "short": True
                        },
                        {
                            "title": "状态",
                            "value": notification.alert.get_status_display(),
                            "short": True
                        },
                        {
                            "title": "时间",
                            "value": notification.alert.event_time.strftime('%Y-%m-%d %H:%M:%S'),
                            "short": True
                        }
                    ],
                    "footer": "DevOps监控系统",
                    "ts": int(notification.alert.event_time.timestamp())
                }
            ]
        }

        # 如果配置了@用户或频道
        if config.get('mentions'):
            mentions = ' '.join([f"<@{mention}>" for mention in config.get('mentions')])
            data['text'] = mentions

        response = requests.post(
            webhook_url,
            json=data,
            headers={'Content-Type': 'application/json'},
            timeout=30
        )

        if not response.ok:
            raise Exception(f"Slack通知发送失败: {response.status_code} {response.text}")

    def retry_failed_notifications(self):
        """重试失败的通知"""
        from ..models import AlertNotification

        # 获取失败的通知（重试次数小于3次）
        failed_notifications = AlertNotification.objects.filter(
            status='failed',
            retry_count__lt=3
        ).order_by('created_at')[:50]  # 限制一次重试50条

        logger.info(f"开始重试 {failed_notifications.count()} 条失败的通知")

        for notification in failed_notifications:
            try:
                # 增加重试次数
                notification.retry_count += 1
                notification.status = 'retry'
                notification.save()

                # 重新发送通知
                self._send_single_notification(notification.alert, notification.channel)

                logger.info(f"重试通知成功: {notification.id}")

            except Exception as e:
                logger.error(f"重试通知失败 {notification.id}: {e}")
                notification.status = 'failed'
                notification.error_message = str(e)
                notification.save()

    def cleanup_old_notifications(self, days: int = 30):
        """清理旧的通知记录"""
        from ..models import AlertNotification

        cutoff_date = timezone.now() - timedelta(days=days)

        deleted_count = AlertNotification.objects.filter(
            created_at__lt=cutoff_date
        ).delete()[0]

        logger.info(f"清理了 {deleted_count} 条 {days} 天前的通知记录")

        return deleted_count

    def retry_failed_notifications(self):
        """重试失败的通知"""
        # 获取需要重试的通知
        retry_notifications = AlertNotification.objects.filter(
            status='retry',
            retry_count__lt=self.max_retry_count,
            created_at__gte=timezone.now() - timedelta(hours=24)  # 只重试24小时内的通知
        )

        for notification in retry_notifications:
            try:
                # 检查是否到了重试时间
                if notification.created_at + timedelta(seconds=self.retry_delay * notification.retry_count) > timezone.now():
                    continue

                # 重新发送通知
                self._send_single_notification(notification.alert, notification.channel)

            except Exception as e:
                logger.error(f"重试通知失败: {e}")
                notification.retry_count += 1
                if notification.retry_count >= self.max_retry_count:
                    notification.status = 'failed'
                notification.save()

    # 测试方法
    def _send_email_test(self, channel: NotificationChannel, subject: str, content: str):
        """发送测试邮件 - 简化版本"""
        # 暂时禁用，使用views.py中的统一邮件发送方法
        pass

    def _send_sms_test(self, channel: NotificationChannel, content: str):
        """发送测试短信"""
        config = channel.config
        provider = config.get('provider', 'aliyun')

        if provider == 'aliyun':
            # 阿里云短信测试
            logger.info(f"阿里云短信测试发送: {content}")
        elif provider == 'tencent':
            # 腾讯云短信测试
            logger.info(f"腾讯云短信测试发送: {content}")
        else:
            raise ValueError(f"不支持的短信提供商: {provider}")

    def _send_webhook_test(self, channel: NotificationChannel, subject: str, content: str):
        """发送测试Webhook"""
        config = channel.config
        webhook_url = config.get('url')

        import requests

        payload = {
            'subject': subject,
            'content': content,
            'timestamp': timezone.now().isoformat(),
            'test': True
        }

        response = requests.post(webhook_url, json=payload, timeout=30)
        response.raise_for_status()
        logger.info(f"Webhook测试发送成功: {webhook_url}")

    def _send_dingtalk_test(self, channel: NotificationChannel, subject: str, content: str):
        """发送测试钉钉消息"""
        config = channel.config
        webhook_url = config.get('webhook_url')

        import requests
        import time
        import hmac
        import hashlib
        import base64
        import urllib.parse

        # 如果配置了签名密钥，需要计算签名
        secret = config.get('secret')
        if secret:
            timestamp = str(round(time.time() * 1000))
            secret_enc = secret.encode('utf-8')
            string_to_sign = '{}\n{}'.format(timestamp, secret)
            string_to_sign_enc = string_to_sign.encode('utf-8')
            hmac_code = hmac.new(secret_enc, string_to_sign_enc, digestmod=hashlib.sha256).digest()
            sign = urllib.parse.quote_plus(base64.b64encode(hmac_code))
            webhook_url = f"{webhook_url}&timestamp={timestamp}&sign={sign}"

        # 获取关键词配置
        keyword = config.get('keyword', 'devops')  # 默认使用devops
        current_time = timezone.now().strftime('%Y-%m-%d %H:%M:%S')

        # 使用markdown格式创建丰富的钉钉测试消息
        formatted_content = f"""{keyword}

# 🎉 DevOps监控系统测试通知

![测试成功](https://img.icons8.com/color/48/000000/checked--v1.png)

---

## 📋 测试详情

| 项目 | 内容 |
|------|------|
| **测试内容** | {content} |
| **通知渠道** | {channel.name} |
| **发送时间** | {current_time} |
| **状态** | ✅ 配置正常 |

---

## 🎯 测试结果

> ### ✅ 恭喜！测试成功
>
> 如果您收到此消息，说明钉钉通知配置**完全正常**！
>
> 🚀 您的DevOps监控系统已准备就绪

---

### 📞 技术支持
- 🌐 **系统**: DevOps监控平台
- 🔧 **版本**: v2.0
- 📧 **支持**: devops@company.com

---
*🤖 DevOps监控系统自动发送 | {current_time}*"""

        payload = {
            'msgtype': 'markdown',
            'markdown': {
                'title': '🔔 DevOps测试通知',
                'text': formatted_content
            }
        }

        # 如果配置了@所有人或特定用户
        if config.get('at_all') or config.get('at_mobiles'):
            payload["at"] = {
                "isAtAll": config.get('at_all', False),
                "atMobiles": config.get('at_mobiles', [])
            }

        # 使用较短的超时时间，避免阻塞
        try:
            response = requests.post(webhook_url, json=payload, timeout=10)
            response.raise_for_status()

            result = response.json()
            logger.info(f"钉钉响应: {result}")

            if result.get('errcode') != 0:
                raise Exception(f"钉钉测试消息发送失败: {result.get('errmsg')}")

            logger.info(f"钉钉测试消息发送成功，响应码: {result.get('errcode')}")
        except requests.exceptions.Timeout:
            logger.warning("钉钉请求超时，但消息可能已发送")
            # 不抛出异常，因为超时不代表发送失败
        except Exception as e:
            logger.error(f"钉钉发送失败: {str(e)}")
            raise

    def _send_wechat_test(self, channel: NotificationChannel, subject: str, content: str):
        """发送测试企业微信消息"""
        config = channel.config
        webhook_url = config.get('webhook_url')

        import requests

        # 使用news格式创建图文消息
        current_time = timezone.now().strftime('%Y-%m-%d %H:%M:%S')

        # 获取监控图表图标
        chart_url = self._get_monitoring_chart_icon()

        payload = {
            "msgtype": "news",
            "news": {
                "articles": [
                    {
                        "title": "📊 DevOps监控系统测试通知",
                        "description": f"""✅ 测试成功！通知配置正常

📋 测试详情：
• 测试内容：{content}
• 通知渠道：{channel.name}
• 发送时间：{current_time}
• 状态：配置正常

📈 监控图表：
上图显示了主机监控概览，包含CPU、内存、磁盘和网络使用情况的24小时趋势图。

🎉 如果您收到此消息，说明企业微信通知配置正常！""",
                        "url": "https://work.weixin.qq.com",
                        "picurl": chart_url
                    }
                ]
            }
        }

        # news格式不支持@特定用户功能

        logger.info(f"企业微信测试请求URL: {webhook_url}")
        logger.info(f"企业微信测试请求数据: {payload}")

        # 使用较短的超时时间，避免阻塞
        try:
            response = requests.post(webhook_url, json=payload, timeout=10)
            response.raise_for_status()

            result = response.json()
            logger.info(f"企业微信响应: {result}")

            if result.get('errcode') != 0:
                raise Exception(f"企业微信测试消息发送失败: {result.get('errmsg')}")

            logger.info(f"企业微信测试消息发送成功，响应码: {result.get('errcode')}")
        except requests.exceptions.Timeout:
            logger.warning("企业微信请求超时，但消息可能已发送")
            # 不抛出异常，因为超时不代表发送失败
        except Exception as e:
            logger.error(f"企业微信发送失败: {str(e)}")
            raise

    def _send_slack_test(self, channel: NotificationChannel, subject: str, content: str):
        """发送测试Slack消息"""
        config = channel.config
        webhook_url = config.get('webhook_url')

        import requests

        payload = {
            'text': subject,
            'attachments': [
                {
                    'color': 'warning',
                    'text': content,
                    'footer': 'DevOps监控系统测试',
                    'ts': int(timezone.now().timestamp())
                }
            ]
        }

        response = requests.post(webhook_url, json=payload, timeout=30)
        response.raise_for_status()
        logger.info(f"Slack测试消息发送成功")
